import format.amf.Value;
import format.flv.Data;
import hxvid.events.ConnectionEvent;
import hxvid.events.SharedObjectEvent;
import hxvid.IApplication;
import hxvid.Client;
import hxvid.Logger;
import hxvid.Rtmp;
import hxvid.Commands;
import hxvid.Data;
import hxvid.Server;
import hxvid.SharedObject;

typedef PlayerData=
{
	var id:Int;
	var name:String;
	var x:Float;
	var y:Float;
}
typedef GameData =
{
	var id : Float;
	var name : String;
	var players : Array<String>;
}
typedef Games =
{
	var lock : neko.vm.Mutex;
	var data : Hash<GameData>;
}
typedef Players =
{
	var lock : neko.vm.Mutex;
	var data : Hash<PlayerData>;
}
typedef Clients =
{
	var lock : neko.vm.Mutex;
	var data : Hash<Client>;
}
class Application implements IApplication
{
	public var config:ApplicationConfig;
	var games:Games;
	var players:Players;
	var clients:Clients;
	
	var server:Server;
	var clientID:Int;
	var logger:Logger;
	var lock : neko.vm.Mutex;
	
	public function new(server:Server, config:ApplicationConfig)
	{
		this.server = server;
		this.config = config;
		this.lock=new neko.vm.Mutex();
		this.clientID = 0;
		//this.clients = new Hash();
		this.players = 
		{
			lock:new neko.vm.Mutex(),
			data:new Hash()
		};
		this.clients = 
		{
			lock:new neko.vm.Mutex(),
			data:new Hash()
		};
		this.games = 
		{
			lock:new neko.vm.Mutex(),
			data:new Hash()
		};
		
		logger = new Logger(config.logFile);
		logger.id = "connect4/Application";
		logger.info("started");
		//server.addEventListener(ServerEvent.CLIENT_CONNECT, onBeforeConnect);
	}
	public function onConnect(client:Client, obj:ConnectionObject, ?arg:Dynamic=null):Void
	{
		logger.info("client:"+ client.ip);
		client.addEventListener(ConnectionEvent.CONNECTED, onConnected);
		client.addEventListener(ConnectionEvent.DISCONNECTED, onPlayerLeave);
		client.addEventListener(SharedObjectEvent.CONNECT, onSharedObjectConnect);
		client.logger.enabled = true;
		client.logger.errorEnabled = true;
		client.addCommands(
		[
			["getLogin", this.onGetLogin, T.Null, T.String ],
			["getGameId", this.onGetGameId, T.Null],
			["getPlayers", this.onGetPlayers, T.Null ],
			["getGamesInPlay", this.onGetGamesInPlay, T.Null ],
			["getHistory", this.onGetHistory, T.Null ],
			["setTextInput", this.onSetTextInput, T.Null, T.String],
			["setGameOponent", this.onSetGameOponent, T.Null, T.Float, T.String]
			//["playerMove", this.onPlayerMove, T.Null, T.Object ],
			
		]);
		client.accept();
	}
	function onConnected(event:ConnectionEvent):Void
	{
		logger.info("client:" + event.target.ip + " has received NetConnection.Connect.Success)");
	}
	function onGetLogin(cinf:CommandInfos, _ ,name:String):Void
	{	
		var client:Client = cinf.c;
		logger.info("client:"+client.ip +", with name:"+ name);
		clients.lock.acquire();
		players.lock.acquire();
		if(clients.data.exists(name))
		{
			client.sendResult(cinf, false);
		}
		else
		{
			client.sendResult(cinf, true);
			
			client.id = clientID++;
			client.name = name;
			clients.data.set(name, client);
			//
			
			
			var player = {
				id:client.id, 
				name:client.name,
				x:0.0,
				y:0.0
			};
			players.data.set(name, player);
			
			for (c in clients.data)
			{
				c.send1("onPlayerJoin", player);
			}
		}
		players.lock.release();
		clients.lock.release();
	}
	public function onPlayerLeave(event:ConnectionEvent)
	{
		var client = event.target;
		logger.info("client:" +client.ip+", id:"+client.id + ", name:" + client.name);
		clients.lock.acquire();
		if(client.id!=null && clients.data.exists(client.name))
		{
			clients.data.remove(client.name);
			players.lock.acquire();
			var data = players.data.get(client.name);
			players.data.remove(data.name);
			players.lock.release();
			for (c in clients.data)
			{
				c.send1("onPlayerLeave", data);
			}
		}
		else
		{
			findAndRemove(client);
		}
		clients.lock.release();
	}
	function onGetPlayers(cinf:CommandInfos, _ ):Void
	{
		var client:Client = cinf.c;
		//logger.info("client:"+client.ip);
		var out:Array<PlayerData> = [];
		players.lock.acquire();
		for (p in players.data)
			out.push(p);
		players.lock.release();
	//	logger.debug("out:"+out);
		client.sendResult(cinf, out);
	}
	function findAndRemove(client:Client)
	{
		for(k in clients.data.keys())
		{
			if(clients.data.get(k)==client)
			{
				clients.data.remove(k);
				return;
			}
		}
	}
	function onGetHistory(cinf:CommandInfos, _ ):Void
	{
		var client:Client = cinf.c;
		//logger.info(client.id);
		var appsDir = server.config.appsDir;
		var appName = server.getApplicationName(this);
		var soDir = config.soDir;
		var path = neko.Sys.getCwd() + appsDir + "/" + appName + "/" + soDir;
		
		var history = new Array();
		if(neko.FileSystem.isDirectory(path))
			for(f in neko.FileSystem.readDirectory(path) )
				if(StringTools.startsWith(f, "so") && StringTools.endsWith(f, ".hxso"))
					history.push({name:f, stat:neko.FileSystem.stat(path+"/"+f)});
		client.sendResult(cinf, history);
	}
	function onSetTextInput(cinf:CommandInfos, _ , text:String):Void
	{
		var client:Client = cinf.c;
		//logger.info("textInput sent by client:"+client.ip +", text:"+ text);
		for (c in clients.data)
		{
			c.send2("onTextInput", {id:client.id, name:client.name}, text);
		}
	}

	function onGetGameId(cinf:CommandInfos, _):Void
	{
		var client:Client = cinf.c;
		var gameId:Float = Date.now().getTime();
		client.sendResult(cinf, gameId);
		
		games.lock.acquire();
		clients.lock.acquire();
		var key:String = Std.string(gameId/1000)+"000";
		var game = {name:key, id:gameId, players:[client.name,null]};
		games.data.set(key, game);
		for (c in clients.data)
		{
			c.send1("onGameJoin", game);
		}
		clients.lock.release();
		games.lock.release();
	}
	function onSetGameOponent(cinf:CommandInfos, _, id:Float, oponent:String):Void
	{
		var client:Client = cinf.c;
		var gameId:Float = id;//Date.now().getTime();
	
		games.lock.acquire();
		clients.lock.acquire();
		var key = Std.string(gameId/1000)+"000";
		var game = games.data.get(key);//{name:Std.string(gameId), id:gameId, players:[client.name,null]};
		//games.data.set(game.name, game);
		game.players[1]=oponent;
		for (c in clients.data)
		{
			c.send1("onGameJoin", game);
		}
		clients.lock.release();
		games.lock.release();
	}
	function onGetGamesInPlay(cinf:CommandInfos, _ ):Void
	{
		var client:Client = cinf.c;
		//logger.info("client:"+client.ip);
		var out:Array<GameData> = [];
		games.lock.acquire();
		for(game in games.data)
		{
			out.push(game);
		}
		client.sendResult(cinf, out);
		games.lock.release();
	}
	function onSharedObjectConnect(event:SharedObjectEvent):Void
	{
		var client = event.target;
		//var so:SOData = event.data.so;
		client.addEventListener(SharedObjectEvent.SUBSCRIBE, onSharedObjectSubscribe);
		client.addEventListener(SharedObjectEvent.DISCONNECT, onSharedObjectDisconnect);
	}
	function onSharedObjectSubscribe(event:SharedObjectEvent):Void
	{
		var client = event.target;
		var so:SharedObject = event.data.so;
		so.addEventListener(SharedObjectEvent.CHANGE, onSharedObjectChange);
		so.addEventListener(SharedObjectEvent.SAVE, onSharedObjectSave);
	}
	function onSharedObjectSave(event:SharedObjectEvent):Void
	{
		
		var so = event.data;
		so.result = format.amf.Tools.decode(so.data.get("gameover"));
		var key = so.name.split('so').join('');
		games.lock.acquire();
		if(games.data.exists(key))
		{
			games.data.remove(key);
		}
		games.lock.release();
		
		clients.lock.acquire();
		for(c in clients.data)
		{
			c.send0("updateHistoryList");
			c.send0("updateGamesInPlayList");
		}
		clients.lock.release();
	}
	function onSharedObjectChange(event:SharedObjectEvent):Void
	{
		var client:Client = event.target;
		var so:SharedObject = event.data.so;
		var a:Array<Dynamic> = event.data.list;
		so.lock.acquire();
		for (i in 0...a.length)
		{
			var key:String = a[i].get("key");
			var value:format.amf.Value = a[i].get("value");
			if(key=="xy")
			{
				switch(so.data.get("moves"))
				{
					case AObject( fields /*: Hash<Value>*/, size /*: Int*/ ):
						fields.set(Std.string(size+1), value);
						so.data.set("moves", AObject(fields, ++size));
					default:
				}
				/*
				var moves:Array<Array<Dynamic>> = format.amf.Tools.decode(so.data.get("moves"));
				var move = format.amf.Tools.decode(value);
				moves.push(move);
				so.data.set("moves", format.amf.Tools.encode(moves));
				*/
			}
		}
		so.lock.release();
	}
	function onSharedObjectDisconnect(event:SharedObjectEvent):Void
	{
		var client = event.target;
		var so = event.data.so;
		//logger.info("so disconnect: so:" +so.name+", client:"+client.name);
	}
	/*
	function onSharedObjectChange1(event:SharedObjectEvent):Void
	{
		var client:Client = event.target;
		var so:SharedObject = event.data.so;
		var key:String = event.data.key;
		var value:format.amf.Value = event.data.value;
		//logger.info("so change: name:" +so.name+", key:"+key+", value:"+value);
		if(key=="xy")
		{
			switch(so.data.get("moves"))
			{
				case AObject( fields : Hash<Value>, size : Int):
					fields.set(Std.string(size+1), value);
					so.data.set("moves", AObject(fields, ++size));
				default:
			}
			var moves:Array<Array<Dynamic>> = format.amf.Tools.decode(so.data.get("moves"));
			var move = format.amf.Tools.decode(value);
			moves.push(move);
			so.data.set("moves", format.amf.Tools.encode(moves));
		}
	}
	*/
}